#include <arch/cpu/soc/s3c2440.h>
#include <arch/io.h>
#include <config.h>
#include <device.h>
#include <uart.h>
#include <limits.h>
#include <errno.h>
#include <clk.h>

static int s3c2440_uart_putc(struct device *dev, unsigned int c)
{
	struct uart_device *uart_dev = dev->priv;
	struct s3c2440_uart *uart_regs = s3c2440_get_base_uart(uart_dev->index);
	
	if (c > UCHAR_MAX) 
		return -EINVAL;

	while (readl(&uart_regs->ufstat) & (1 << 14));
	writeb((uint8_t)c, &uart_regs->utxh);

	if (c == '\n') 
		s3c2440_uart_putc(dev, '\r');

	return 0;
}

static int s3c2440_uart_getc(struct device *dev)
{
	struct uart_device *uart_dev = dev->priv;
	struct s3c2440_uart *uart_regs = s3c2440_get_base_uart(uart_dev->index);
	
	while ((readl(&uart_regs->ufstat) & 0x3f) == 0);
	return readb(&uart_regs->urxh);
}

static void s3c2440_uart_init(struct device *dev)
{
	struct uart_device *uart_dev = dev->priv;
	struct s3c2440_uart *uart_regs = s3c2440_get_base_uart(uart_dev->index);
	struct s3c2440_gpio *gpio = s3c2440_get_base_gpio();
	uint32_t tmp;
	struct clk *clk;

	clk = clk_get_by_id(CLK_UART0 + uart_dev->index);
	if (IS_ERR(clk_enable(clk))) {
		while (1);
	}
	clk = clk_get_by_id(CLK_GPIO);
	if (IS_ERR(clk_enable(clk))) {
		while (1);
	}

	tmp  = readl(&gpio->gphcon) & ~(0xf << (4 + uart_dev->index * 4));
	tmp |= (0xa << (4 + uart_dev->index * 4));
	writel(tmp, &gpio->gphcon);
	tmp  = readl(&gpio->gphup) & ~(3 << (2 + uart_dev->index * 2));
	writel(tmp, &gpio->gphup);
	
	writel(0x7, &uart_regs->ufcon);		// FIFO enable, Tx/Rx FIFO clear
	if (uart_dev->index != 2) {
		writel(0, &uart_regs->umcon);	// no auto flow control
	}
	writel(0x3, &uart_regs->ulcon);		// no parity, 1 stop, 8 bit
	writel(0x5, &uart_regs->ucon);		// polling mode, PCLK for baudrate

	clk = clk_get_by_id(CLK_PCLK);
	tmp = clk_get_rate(clk) / (16 * CONFIG_BAUDRATE) - 1;

	writel(tmp, &uart_regs->ubrdiv);
}

struct uart_device s3c2440_uart = {
	.index = CONFIG_CONSOLE_UART,
	.init  = s3c2440_uart_init,
	.getc  = s3c2440_uart_getc,
	.putc  = s3c2440_uart_putc,
};

DEVICE(DEVICE_UART, -1, &s3c2440_uart);

